iT邦幫忙

2022 iThome 鐵人賽

DAY 15
0
Modern Web

Three.js 學習日誌系列 第 15

Day14 - 金屬球範例試作(1) - Material解密(四)

  • 分享至 

  • xImage
  •  

Day14 - 金屬球範例試作(1) - Material解密(四)

這裡是「Three.js學習日誌」的第14篇,本篇的主旨是要透過一個簡單的範例操作,來一步一步介紹如何做出更為真實的質感,這系列的文章假設讀者看得懂javascript,並且有Canvas 2D Context的相關知識。

到目前為止的系列文中,我們基本上只有使用過MeshStandardMaterial還有MeshBasicMaterial。所以接下來的2天我打算透過試做一個簡單的小場景,來學習材質與渲染的關係。

試著做一個放置在金屬面上的金屬球

這次我們先從一個只有cameraScene開始。

為了節省篇幅,初始化的環節也是先略過了。

1. 先把球&平面都置入Scene當中

const geo = new SphereGeometry(1, 100, 100);
const planeGeo = new PlaneGeometry(20, 20, 20, 20);
const mat1 = new MeshStandardMaterial({color:new Color('#eee')});
const mat2 = new MeshStandardMaterial({color:new Color('#eee')});
const mesh = new Mesh(geo, mat1);
const planeMesh = new Mesh(planeGeo, mat2);
scene.add(mesh, planeMesh);

當然這時候還是黑一片,因為沒有光源

2. 來點光

const al = new AmbientLight(0xffffff,1)
const pl = new PointLight(0xffffff, 0.3);
pl.position.set(3, 3, 3);
scene.add(pl,al);

備註: 光源這邊原本漏掉環境光,我在2022/10/1晚上有重新修正錯誤的部分,重新補上環境光,並且下修點光源的強度。
img

3. 旋轉平面,調整球的位置

因為PlaneGeometry在一開始建立的時候會是直立的(而不是橫躺),所以這邊我們必須要來點旋轉。

這邊我們可以直接使用Object3D.lookAt,這個方法其實就跟字面上的意思一樣,可以讓Object3D物件「看」向某個目標座標。這邊我們讓平面朝向(0,1,0),也就是說面會朝上。

然後再稍微調整一下球的位置到(0,1,0),讓它剛好落在平面上(半徑是1)

planeMesh.lookAt(0,1,0);
mesh.position.set(0,1,0);

img

4. 使用 OrbitControl

這邊我們又要來超前一下進度了。

因為現在平面是完全垂直於我們的螢幕,所以看起來像一條線。

但這樣實在太無趣,所以我們要給他來點可控性

OrbitControl就是環形攝影機軌道的意思,Three.js有提供這樣的機能讓我們可以用滑鼠直接操作camera,讓我們可以從不同的角度去觀察我們渲染的Scene。

作法很簡單,首先我們要先建立OrbitControl的實例,接著把camerarenderer.domElement當作參數傳入。

這邊因為cdn.skypack.devThree.js Package沒有提供OrbitControl的Module,所以我們必須要引用別的Package。

正常來講Three.js 的npm package底下是可以找到OrbitControl的,我們之後會再提到。

import threeTsOrbitControls from 'https://cdn.skypack.dev/@three-ts/orbit-controls';

const controls = new threeTsOrbitControls.OrbitControls(camera, renderer.domElement)

接著我們得在tick loop(也就是requestAnimationFrame的迴圈)裡面呼叫Controls.update

 let time = 0;
  const loop = (time) => {
    mesh.rotation.y = time / 1000;
    controls.update();
    renderer.render(scene, camera);
    requestAnimationFrame((time) => {
      loop(time);
    });
  };

  loop();

這樣就可以動手旋轉畫面了。

img

最後我們可以調整一下camera的位置,讓畫面不要看起來這麼無趣。

這邊其實有個小撇步,假如我在利用OrbitControls旋轉角度,感覺有某個角度特別中意的話,我們其實可以把camera暴露為全域物件。

 window.cm = camera;

這樣我們就可以先使用OrbitControls把畫面轉到我們喜歡的位置,然後用開發者工具印出來camera目前的位置,接著再把這些數據紀錄到程式裡面。

img

camera.position.set(0.8182387794884614,3.0688847649716244,3.8616617665282886)

這樣攝影機就會以我們中意的位置作為初始狀態來呈現了~

5. 調整金屬化/粗糙度參數,然後調整一下光照

馬上來試玩一下~上一篇介紹的金屬化/粗糙度,這兩個屬性可以讓我們調整Material的反射率/漫反射率。

const mat1 = new MeshStandardMaterial({
    color: new Color("#eee"),
    metalness: 0.5,
    roughness: 0.3
});
const mat2 = new MeshStandardMaterial({
    color: new Color("#eee"),
    metalness: 0.2,
    roughness: 0.8
});
const mesh = new Mesh(geo, mat1);
const planeMesh = new Mesh(planeGeo, mat2);
...
const al = new AmbientLight(0xffffff, 1); //補一個環境光
const pl = new PointLight(0xffffff, 0.3); //減弱點光源的強度
scene.add(al);

img

6. 動態渲染陰影

接著又是要來超前進度啦~

做到這邊為止雖然好像有越來越像一回事(?)

但各位大概會發現球有時感覺像是飄在空中的。

這是因為我們缺乏了陰影

Three.js中,如果我們想要透過現有的光源來運算物體實時的陰影,那首先我們會需要做兩件事。

  • 光源&會造成陰影的物體必須要設定castShadow

  • 上面會有陰影的物體必須要設定receiveShadow

所以這邊我們必須給PointLight設定castShadow,並且給平面設定receiveShadow

mesh.castShadow = true;
...
planeMesh.receiveShadow = true;
...
pl.castShadow = true;

設定完castShadow/receiveShadow之後,接著則是要打開renderershadowMap設置,並且把shadow type 改為PCFSoftShadowMap

renderer.shadowMap.enabled = true;
renderer.shadowMap.type = PCFSoftShadowMap // PCFSoftShadowMap記得要從module import

img

這樣就有影子了~

但是在這邊我們可以注意到,陰影的邊緣似乎有點瑕疵。

這個問題發生的原因其實是因為燈光的shadow map解析度不夠,所以我們要再調整一下。

pl.shadow.mapSize.width = 2048;
pl.shadow.mapSize.height = 2048;

img

Perfect Shadow !

小結

到這邊我們已經做出來一個簡單的小場景,明天我們會繼續用這個範例來介紹環境貼圖和一些我們目前尚未使用過的材質


上一篇
Day13 - 「紋理&種類」- Material解密(三)
下一篇
Day15 - 金屬球範例試作(2) - Material解密(五)
系列文
Three.js 學習日誌31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言